class Agent(object):
    
    NAME = "FROSTIES"
    

    # =============== global variables =================
    FIRST_OBJECTIVE = None
    NEW_OBJECTIVE = 0
    GAME_STARTED = False
    AMMO_LOCATIONS = []
    UNOC = []
    TARGETS = []   # shooting targets
    ENEMY_BASE_COORDS = []
    # ==================================================


    
    def __init__(self, id, team, settings=None, field_rects=None, field_grid=None, nav_mesh=None, blob=None):
        """ Each agent is initialized at the beginning of each game.
            The first agent (id==0) can use this to set up global variables.
            Note that the properties pertaining to the game field might not be
            given for each game.
        """
        self.id = id
        self.team = team
        self.mesh = nav_mesh
        self.grid = field_grid
        self.settings = settings
        self.goal = None
        
        # Read the binary blob, we're not using it though
        if blob is not None:
            print "Agent %s-%d received binary blob of %s" % (
                ('BLU' if team else 'RED'), id, type(pickle.loads(blob.read())))
            # Reset the file so other agents can read it.
            blob.seek(0) 
        
        # Recommended way to share variables between agents.
        if id == 0:
            self.all_agents = self.__class__.all_agents = []
        self.all_agents.append(self)
    
  
    def observe(self, observation):
        """ Each agent is passed an observation using this function,
            before being asked for an action. You can store either
            the observation object or its properties to use them
            to determine your action. Note that the observation object
            is modified in place.
        """
        self.observation = observation
        self.selected = observation.selected
        obs = observation
        
        
        # Set the global opponent base coordinates
        if self.__class__.GAME_STARTED == False:
            
            self.letTheBaseKick()
            
            if self.id==5: 
                self.__class__.GAME_STARTED = True
        
        if observation.selected:
            pass
            # print observation
           
	    


# Cem's functions =========================

    """ This function should return a list of object
        tuples, each tuple containing the closest for
        certain "object", as listed below:

    """
    #closestSth[0][0:2] for cps
    #closestSth[1][0:2] for ammo
    #closestSth[2][0:2] for foe
    def get_closest(self):
     
        #friend = point_dist(obs.loc, obs.friends)
        obs = self.observation
        
        closestAmmo = []
        closestFoe = []
        closestAll = []

    
        cpDists = []                                        #cpDists holds the distances of each ctrl point to the agent
        if obs.cps != []:
            for x in obs.cps:
                cpDists.append(point_dist(x[0:2],obs.loc))
                
            
            closestCpindex = cpDists.index(min(cpDists))
            closestCp = obs.cps[closestCpindex]             #the closest Cp


        ammoDists = []                                      #ammoDists holds the distances of each ammo to the agent
        if obs.objects != []:
            for x in obs.objects:
                ammoDists.append(point_dist(x[0:2],obs.loc))

            
            closestAmmoindex = ammoDists.index(min(ammoDists))
            closestAmmo = obs.objects[closestAmmoindex]             #the closest ammo
            

        foeDists = [] 
        if obs.foes != []:
            for x in obs.foes:
                foeDists.append(point_dist(x[0:2],obs.loc))

            
            closestFoeindex = foeDists.index(min(foeDists))
            closestFoe = obs.foes[closestFoeindex]             #the closest ammo


##        allDists = []
##        for x in cpDists:
##            allDists.append(x)
##
##        for x in ammoDists:
##            allDists.append(x)
##
##        for x in foeDists:
##            allDists.append(x)

        #print "     "
        #print cpDists
        #print allDists
            

            
        #closestAllindex = allDists.index(min(allDists))
        #closestAll = obs.cps[closestAllindex]           #the closest Cp
        
            

        #return (closestCp,closestAmmo,closestFoe,closestAll)
        return (closestCp,closestAmmo,closestFoe)



    def get_closest_unoc_cp(self):
        obs = self.observation
        cps = obs.cps[:]
        #cpDists = []
        toSort = []
           
        for x in obs.cps:
            #cpDists.append(point_dist(x[0:2],obs.loc))
            toSort.append((x[0],x[1],x[2],point_dist(x[0:2],obs.loc)))

        

        sortedForDist = sorted(toSort, key=lambda closest: closest[3])

        if sortedForDist[0][2] != self.team:
            closestCp=sortedForDist[0][0:2]

        elif sortedForDist[1][2] != self.team:
            closestCp=sortedForDist[1][0:2]
            
        elif sortedForDist[2][2] != self.team:
            closestCp=sortedForDist[2][0:2]
        else:
            closestCp=-1;
        
          
        return(closestCp)
        
    
    
    
    
    
    #Get the opponent base coord.
    #Finding the upper-left and lower-right corners of the base by using the agent locations
    #Then we use symmetry to locate the enemy-base.. mwahahaha
    def letTheBaseKick(self):
        obs = self.observation
        upperLeftMirror=[]
        lowerRightMirror=[]
   
        #Getting the upper left corner coordinates of our base by using the 0th agent
        if self.id == 0:
            upperLeft = obs.loc
            
            upperLeftMirror.append(656 - upperLeft[0] - 6)  # Mirroring
            upperLeftMirror.append(upperLeft[1] - 6)  # THE WIDTH OF THE SCREEN IS HARDCODED HERE. THIS SHOULD BE GENERIC!!!!
            self.__class__.ENEMY_BASE_COORDS.append(upperLeftMirror)
            #print upperLeftMirror
            
        #Similarly, getting the lower right corner coordinates of our base by using the 5th agent
        if self.id == 5:
            lowerRight = obs.loc
            lowerRightMirror.append(656 - lowerRight[0] + 6)
            lowerRightMirror.append(lowerRight[1] + 6)
            self.__class__.ENEMY_BASE_COORDS.append(lowerRightMirror)
            #print lowerRightMirror
            
    
    
    
    
    
    
    def path_length(self):    # compute the number of steps in a path
        if path:
            step = 0
            numTurn = len(path)
            for i in xrange(0,numTurn):
                if i == 0:
                    step = step + round(sqrt((path[0][0] - obs.loc[0])**2 + (path[0][1] - obs.loc[1])**2)/40 + 0.5)
                else:
                    step = step + round(sqrt((path[i][0] - path[i-1][0])**2 + (path[i][1] - path[i-1][1])**2)/40 + 0.5)
            step = step + numTurn
            return(step)
    
    
    def action(self):
        obs = self.observation
        shoot = False
        speed = 40
        baseAlert = 0  #used for checking whether the enemy is in the base
        
        
        
        if self.id == 0:
            self.__class__.TARGETS = []   # every timestep reset list of targets to empty list
        
        
        ## ======= if ammo point in objects list is not present in AMMO_LOCATIONS list, then add it:
        
        if len(self.__class__.AMMO_LOCATIONS) < 6:   # if all ammo locations have been found, stop checking
            for x in obs.objects:
                if x not in self.__class__.AMMO_LOCATIONS:
                    self.__class__.AMMO_LOCATIONS.append(x)

                    # now handle the ammo on the opposite half of screen
                    mirrorX = 656 - x[0]  # THE WIDTH OF THE SCREEN IS HARDCODED HERE. THIS SHOULD BE GENERIC!!!!
                    mirrorY = x[1]
                    mirror = (mirrorX, mirrorY, x[2])
                    if mirror not in self.__class__.AMMO_LOCATIONS:
                        self.__class__.AMMO_LOCATIONS.append(mirror)



# by default, assign each agent to the closest uncaptured control point.
# This will be overwritten later in the code though

        Goal = self.get_closest_unoc_cp()
        if Goal != -1:
            self.goal = Goal

            
 


# Assign the first three agents a specific priority control point.
# Also, the second and third agent will look for ammo when all 3 control points are captured
        
        if self.id == 0:
            if obs.cps[0][2] == self.team:
                Goal = self.get_closest_unoc_cp()
                if Goal != -1:  # if not dominating all 3 control points, go to closest unoc cp
                    self.goal = Goal
                else:
                    self.goal = obs.cps[0][0:2]     # however, if we are dominating all 3 then go to assigned control point
            else:
                self.goal = obs.cps[0][0:2]

        if self.id == 1:
            if obs.cps[1][2] == self.team:
                Goal = self.get_closest_unoc_cp()
                if Goal != -1:  # if not dominating all 3 control points
                    self.goal = Goal
                elif len(self.__class__.AMMO_LOCATIONS) > 1 and obs.ammo < 1:
                    self.goal = self.__class__.AMMO_LOCATIONS[0][0:2]   # pick up ammo if dominating all 3 cp and agent has no ammo
                else:
                    self.goal = obs.cps[1][0:2] # if all cps are captured and agent has ammo, stay at assigned cp
            else:
                self.goal = obs.cps[1][0:2]
           

        if self.id == 2:
            if obs.cps[2][2] == self.team:
                Goal = self.get_closest_unoc_cp()
                if Goal != -1:  # if not dominating all 3 control points
                    self.goal = Goal
                elif len(self.__class__.AMMO_LOCATIONS) > 3 and obs.ammo < 1:
                    self.goal = self.__class__.AMMO_LOCATIONS[2][0:2]   # pick up ammo if dominating all 3 cp and agent has no ammo
                else:
                    self.goal = obs.cps[2][0:2] # if all cps are captured and agent has ammo, stay at assigned cp
            else:
                self.goal = obs.cps[2][0:2]


            


# hardcode 3 agents to sit on opponent ammo
# first check the length of ammo list to see if enough ammo locations have been discovered
# if agent has too much ammo, don't camp anymore



        # the following code is for the last 3 agents to camp the ammo spawns in the enemy half ## ITS NOT REALLY HELPING THE TEAM
        
        # if obs.cps[0][2] != self.team and obs.cps[1][2] != self.team and obs.cps[2][2] != self.team:
        #    if we are not controlling any of the 3 cps then do not camp
            # pass
        # else:
        
        if self.id == 3:
            if len(self.__class__.AMMO_LOCATIONS) > 1 and obs.ammo < 10:
                self.goal = self.__class__.AMMO_LOCATIONS[1][0:2] # index 1 is first ammo in enemy territory

        if self.id == 4:
            if len(self.__class__.AMMO_LOCATIONS) > 3 and obs.ammo < 10:
                self.goal = self.__class__.AMMO_LOCATIONS[3][0:2] 

        if self.id == 5:
            if len(self.__class__.AMMO_LOCATIONS) > 5 and obs.ammo < 10:
                self.goal = self.__class__.AMMO_LOCATIONS[5][0:2]




        # ===================================
        # THE FOLLOWING APPLIES TO ALL AGENTS
        # ===================================

        
        
        
        # ============================
        # PICKING UP AMMO
        # ============================
        
        if self.id < 3:
            # only the first three agents will pick up ammo along the way
            # Walk to ammo
            ammopacks = filter(lambda x: x[2] == "Ammo", obs.objects)
            if ammopacks and obs.ammo < 1:     # only go for ammo if agent has less than 1 shots
                self.goal = self.get_closest()[1][0:2]
                

                
        # ============================
        # SHOOT ENEMY
        # ============================
        shoot = False
        shootingEnemy = False
        friendlyFireCheck = 0
        opponent_distance = 1000 # this is just to initialize it
        
        #Getting the closest enemy location once
        enemyLoc = self.get_closest()[2][0:2]
        
        if (obs.ammo > 0 and 
            obs.foes and 
            point_dist(self.get_closest()[2][0:2], obs.loc) <= self.settings.max_see     # check distance between closest opponent and self
            and not line_intersects_grid(obs.loc, self.get_closest()[2][0:2], self.grid, self.settings.tilesize)):

            
            
            for o in obs.friends:       # check for friendly fire
                # line_intersects_rect(p0,p1,(o._x,o._y,o.width,o.height))
                if(line_intersects_circ(obs.loc, self.get_closest()[2][0:2], o[0:2], 6)):    # I believe we need to use half the tank's width and height. width=height=12
                    friendlyFireCheck += 1
                    # if there is a team mate in the way then friendlyFireCheck will be bigger than 0
     
            
            opponent_distance = point_dist(enemyLoc, obs.loc)
            
            # # # # # _ _ _ _   check enemy in base _ _ _ _ _ 
            
            #print enemyLoc
            if (enemyLoc[0]<self.__class__.ENEMY_BASE_COORDS[0][0]+10           # checking whether the enemy is in its base
                and enemyLoc[0]>self.__class__.ENEMY_BASE_COORDS[1][0]-10       # (with a certain tolerance of 10 since the rectangle corners
                and enemyLoc[1]>self.__class__.ENEMY_BASE_COORDS[0][1]-10       # are based on the agents)
                and enemyLoc[1]<self.__class__.ENEMY_BASE_COORDS[1][1]+10):     # if enemy's in the base, set baseAlert=1, else baseAlert=0
                baseAlert = 1
               
            #print baseAlert
            #print self.__class__.ENEMY_BASE_COORDS
           
                
            # # # # # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
            
            
            # HERE STILL ADD A CHECK IF ENEMY IS IN BASE
            if baseAlert == 0:
                self.goal = self.get_closest()[2][0:2]
            else:
                self.goal = obs.loc # if enemy is in base, then just wait for it to come out
            
            if (friendlyFireCheck < 1                                                            # if no friends are going to get shot
                and opponent_distance <= self.settings.max_range                                 # and if opponent is within shooting range 
                and baseAlert == 0):                                                             # and if opponent is not in base - i.e. not invincible after spawning            
         
                if self.goal not in self.__class__.TARGETS:     # only shoot if none of the earlier team mates are shooting the same target
                    self.__class__.TARGETS.append(self.goal)    # add the target to the targets list so that next agents can see what he is shooting
                    shoot = True
                    shootingEnemy = True
                



        # Compute path, angle and drive
        path = find_path(obs.loc, self.goal, self.mesh, self.grid, self.settings.tilesize)
        if path:
            dx = path[0][0]-obs.loc[0]
            dy = path[0][1]-obs.loc[1]
            turn = angle_fix(math.atan2(dy, dx)-obs.angle)
            if turn > self.settings.max_turn or turn < -self.settings.max_turn:
                shoot = False
                if shootingEnemy == True:
                    speed = 0                            # if trying to shoot enemy, only turn without moving position
                else:
                    speed = ((dx**2 + dy**2)**0.5) / 3   # use a smaller moving step if turn is too big so that agent does not understeer
            else:
                speed = (dx**2 + dy**2)**0.5
        else:
            turn = 0
            speed = 0
        
        
        if path:
            numTurn = len(path)
            step = numTurn
            dx = path[0][0]-obs.loc[0]
            dy = path[0][1]-obs.loc[1]
            if dx == 0 and dy == 0 and numTurn == 1:
                step = 0  
            else:    
                for i in xrange(0,numTurn):
                    if i == 0:                   
                        step = step + round(sqrt(dx**2 + dy**2)/40 + 0.5)
                    else:
                        step = step + round(sqrt((path[i][0] - path[i-1][0])**2 + (path[i][1] - path[i-1][1])**2)/40 + 0.5)

            
            
            # print " "
            # print "agent id:" + str(self.id)
            # print step
           
        
        
        
        
        # # if the agent is going to stand still, then face enemy base
        # pointToFace = ((self.__class__.ENEMY_BASE_COORDS[0][0]+self.__class__.ENEMY_BASE_COORDS[1][0])/2,(self.__class__.ENEMY_BASE_COORDS[0][1]+self.__class__.ENEMY_BASE_COORDS[1][1])/2)
        
        # if self.goal == obs.loc:    
            # base_path = find_path(obs.loc, pointToFace, self.mesh, self.grid, self.settings.tilesize)
            # if base_path:
                # dx = path[0][0]-obs.loc[0]
                # dy = path[0][1]-obs.loc[1]
                # turn = angle_fix(math.atan2(dy, dx)-obs.angle)
        
        
        
        
        
        return (turn,speed,shoot)







        
    def debug(self, surface):
        """ Allows the agents to draw on the game UI,
            Refer to the pygame reference to see how you can
            draw on a pygame.surface. The given surface is
            not cleared automatically. Additionally, this
            function will only be called when the renderer is
            active, and it will only be called for the active team.
        """
        import pygame
        # First agent clears the screen
        if self.id == 0:
            surface.fill((0,0,0,0))
        # Selected agents draw their info
        if self.selected:
            if self.goal is not None:
                pygame.draw.line(surface,(0,0,0),self.observation.loc, self.goal)
        
    def finalize(self, interrupted=False):
        """ This function is called after the game ends, 
            either due to time/score limits, or due to an
            interrupt (CTRL+C) by the user. Use it to
            store any learned variables and write logs/reports.
        """
        pass
